<?php

namespace Hilaoyu\LaravelExtend\Services\Auth;


use Hilaoyu\LaravelExtend\Services\Captcha\CaptchaService;
use Hilaoyu\LaravelExtend\Supports\Uuid;
use Hilaoyu\Utils\Traits\ErrorsTrait;
use Hilaoyu\Utils\UtilArr;
use Hilaoyu\Utils\UtilRegex;
use Hilaoyu\Utils\UtilSupport;
use Illuminate\Auth\SessionGuard;
use Illuminate\Auth\TokenGuard;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;

class AuthService
{
    static protected $_logouting = false;
    static protected $_login_refreshed = false;
    static protected $callbacks_authenticated_alter = [];
    static protected $callbacks_login_refreshed_alter = [];
    static protected $callbacks_register_before = [];
    static protected $callbacks_check_password_handle = [];
    static protected $callbacks_registered_alter = [];
    static protected $callbacks_logouted_alter = [];
    static protected $callbacks_retrieve_by_credentials;
    static protected $callbacks_validate_credentials_callback;
    static protected $callbacks_password_encryption_callback;
    static protected $callbacks_authenticated_api_return_data;

    static protected $_authenticated_guard_user=[];
    use ErrorsTrait;

    //登录成功后的事件，如果回调返回 false,则返回登录失败
    static public function registerCallbackAuthenticated(\Closure $callback, string $key = '')
    {
        $key = trim($key);
        if ($key) {
            static::$callbacks_authenticated_alter[$key] = $callback;
        } else {
            static::$callbacks_authenticated_alter[] = $callback;
        }
    }

    //每次检查用户登录有效时事件
    static public function registerCallbackLoginRefreshed(\Closure $callback, string $key = '')
    {
        $key = trim($key);
        if ($key) {
            static::$callbacks_login_refreshed_alter[$key] = $callback;
        } else {
            static::$callbacks_login_refreshed_alter[] = $callback;
        }
    }

    //注册后的事件，如果回调返回 false,则返回注册失败
    static public function registerCallbackRegisterBefore(\Closure $callback, string $key = '')
    {
        $key = trim($key);
        if ($key) {
            static::$callbacks_register_before[$key] = $callback;
        } else {
            static::$callbacks_register_before[] = $callback;
        }
    }

    //修改密码前事件，回调返回 true则通过,返回字符串为错误信息
    static public function registerCallbackCheckPasswordHandle(\Closure $callback, string $key = '')
    {
        $key = trim($key);
        if ($key) {
            static::$callbacks_check_password_handle[$key] = $callback;
        } else {
            static::$callbacks_check_password_handle[] = $callback;
        }
    }

    //注册后的事件，如果回调返回 false,则返回注册失败
    static public function registerCallbackRegistered(\Closure $callback, string $key = '')
    {
        $key = trim($key);
        if ($key) {
            static::$callbacks_registered_alter[$key] = $callback;
        } else {
            static::$callbacks_registered_alter[] = $callback;
        }
    }

    //退出登录后的事件
    static public function registerCallbackLogouted(\Closure $callback, string $key = '')
    {
        $key = trim($key);
        if ($key) {
            static::$callbacks_logouted_alter[$key] = $callback;
        } else {
            static::$callbacks_logouted_alter[] = $callback;
        }
    }
    //验证密码前事件
    static public function registerCallbackRetrieveByCredentialsCallback(\Closure $callback)
    {
        static::$callbacks_retrieve_by_credentials = $callback;
    }
    //验证密码方法
    static public function registerCallbackValidateCredentialsCallback(\Closure $callback)
    {
        static::$callbacks_validate_credentials_callback = $callback;
    }
    //密码加密方法
    static public function registerPasswordEncryptionCallback(\Closure $callback)
    {
        static::$callbacks_password_encryption_callback = $callback;
    }
    //登录返回密码方法
    static public function registerCallbackAuthenticatedApiReturnData(\Closure $callback)
    {
        static::$callbacks_authenticated_api_return_data = $callback;
    }

    static public function SetLogouting($ing = true)
    {
        static::$_logouting = (bool)$ing;
    }

    static public function IsLogouting()
    {
        return static::$_logouting;
    }

    static public function GuardKey()
    {
        $guardKey = \request()->route('guard', '');
        if ($guardKey) {
            Auth::shouldUse($guardKey);
        } else {
            $guardKey = Auth::getDefaultDriver();
        }


        return $guardKey;
    }

    static public function Guard($guard_key = null)
    {
        return UtilSupport::tryQuiet(function () use ($guard_key) {
            return Auth::guard($guard_key);
        });

    }

    static public function GuardProvider(string $guard_key='')
    {
        if (!$guard = static::Guard($guard_key)) {
            return null;
        }
        return UtilSupport::tryQuiet(function () use ($guard) {
            return $guard->getProvider();
        });

    }

    static public function GetGuardConfig(string $guard_key, string $key = '')
    {
        $guard_key = trim($guard_key);
        $key = trim($key);
        $config_key = 'auth.guards.' . $guard_key;
        if ('' != $key) {
            $config_key .= '.' . $key;
        }

        return config($config_key);
    }


    static public function GetProviderConfig(string $provider, string $key = '')
    {
        $provider = trim($provider);
        $key = trim($key);
        $config_key = 'auth.providers.' . $provider;
        if ('' != $key) {
            $config_key .= '.' . $key;
        }

        return config($config_key);
    }

    static public function GetGuardProviderConfig(string $guard_key, string $key = '')
    {
        return static::GetProviderConfig(static::GetGuardConfig($guard_key, 'provider'), $key);
    }

    static public function PasswordEn(string $password,string $guard_key=''){
        $enPass = '';
        if(is_callable($callback = static::$callbacks_password_encryption_callback)){
            $enPass = $callback($password,$guard_key);
        }else{
            $provider = AuthService::GuardProvider($guard_key);
            $enPass = $provider->getHasher()->make($password);
        }
        return $enPass;
    }


    static public function CheckPasswordHandle(&$password,&$user=null){
        foreach ((array)static::$callbacks_check_password_handle  as $callback){
            if(true !== ($check = $callback($password,$user))){
                return  $check;
            }
        }

        return true;
    }

    static public function GuardCheckCredentials(string $guard_key, $credentials, $checkPass = true)
    {
        if (!($provider = static::GuardProvider($guard_key))) {
            return null;
        }

//        $user = UtilSupport::tryQuiet(function () use ($provider, $credentials, $checkPass,$guard_key) {
            if(is_callable($callback = static::$callbacks_retrieve_by_credentials)){
                $user = $callback($credentials, $guard_key);
            }else {

                $user = $provider->retrieveByCredentials($credentials);
            }

//            dd($credentials,$user);exit();

            if ($user && $checkPass) {
                $checkPassStatus = false;
                if(is_callable($callback = static::$callbacks_validate_credentials_callback)){
                    $checkPassStatus =  $callback($user,$credentials);
                }else{
                    $checkPassStatus = $provider->validateCredentials($user, $credentials);
                }

                if(!$checkPassStatus){
                    return null;
                }

            }
            return $user;
//        });

        if (!$user) {
            return null;
        }


        return $user;
    }

    static public function LoginByCredentials(string $guard_key, $credentials, $checkPass = true)
    {
        $user = static::GuardCheckCredentials($guard_key, $credentials, $checkPass);
        if (!$user) {
            return null;
        }

        return static::Login($guard_key, $user);
    }

    static public function Login(string $guard_key, AuthenticatableContract $user)
    {
        if (!$user) {
            return null;
        }
        if (!data_get($user, 'status', false)) {
            static::SetStaticError('账号被禁用，请联系管理员', 'user_status_error');
            //static::LogOut($guard);
            return null;
        }

        if (!$user = static::ClearExistedToken($user, $guard_key)) {
            return null;
        }
        if (!$guard = static::Guard($guard_key)) {
            return null;
        }
        Auth::shouldUse($guard_key);

        $token = UtilSupport::tryQuiet(function () use (&$user) {
            $token = $user->getAuthenticatedToken();
            if (!$token) {
                $token = $user->generateToken();
                $user->setAuthenticatedToken($token);
            }
            return $token;
        });
        if (!$token) {
            return null;
        }

        if ($guard instanceof SessionGuard) {
            $guard->login($user);
            $guard->getSession()->put($guard->getName() . '_token_id', data_get($token, 'id', ''));
            $guard->getSession()->put('login_client_source', request()->getClientSource());
            $guard->getSession()->put('login_client_platform', request()->getClientPlatform());
        } else {
            UtilSupport::tryQuiet(function () use ($guard, $user) {
                $guard->setUser($user);
            });
        }

        UtilSupport::tryQuiet(function () use (&$user) {
            $user->clearOwnedAccessesCache();
        });



        //执行事件
        if (!empty(static::$callbacks_authenticated_alter) && $user) {
            foreach (static::$callbacks_authenticated_alter as $callback) {
                if (!is_callable($callback)) {
                    continue;
                }
                if (false === $callback($user, $token)) {
                    static::LogOut($guard_key);
                    return null;
                }
            }
        }


        //记录日志
        static::RecordLoginLog($guard_key,$user,$token);

        return $user;

    }


    /**
     * 记录日志
     * @param $user
     * @param null $token
     * @param string $app_id
     */
    static public function RecordLoginLog($guard_key,$user,$token=null,$app_id=''){
        if ($login_log_model_name = static::GetGuardProviderConfig($guard_key, 'model_login_log')) {

            //dd($login_log_model_name);
            UtilSupport::tryQuiet(function () use ($login_log_model_name, $user, $token,$app_id) {
                UtilSupport::makeClass($login_log_model_name)->fill([
                    'token_id' => data_get($token, 'id', ''),
                    'user_id' => data_get($user, 'id', ''),
                    'user_model' => get_class($user),
                    'app_id' => trim($app_id),
                    'login_platform' => request()->getClientPlatform(),
                    'login_source' => request()->getClientSource(),
                    'login_ip' => request()->ip(),
                    'referer' => request()->header('referer'),
                ])->save();
            });
        }
    }

    static public function LoginRefresh($user, $token = null)
    {
        //return $user;
        if(static::$_login_refreshed){
            return $user;
        }
        static::$_login_refreshed = true;
        if (!static::IsLogouting()) {
            //执行事件
            if (!empty(static::$callbacks_login_refreshed_alter) && $user) {
                foreach (static::$callbacks_login_refreshed_alter as $callback) {
                    if (!is_callable($callback)) {
                        continue;
                    }
                    if (false === $callback($user, $token)) {
                        static::LogOut();
                        $user = null;
                        $token = null;
                        break;
                    }
                }
            }
        }

        // file_put_contents(storage_path("aaa.txt"),Carbon::now()->toDateTimeString() . ($user ? " user ok":" user no").PHP_EOL,FILE_APPEND);

        //更新用户表
        UtilSupport::tryQuiet(function () use ($user) {
            if ($user) {
                $user->loginRefresh();
            }
        });
        //更新token表
        UtilSupport::tryQuiet(function () use ($token) {

            if ($token) {
                $expire_time = data_get($token, 'expire_time');
                if ($expire_time && now()->diffInMinutes($expire_time) <= 10) {
                    $token->loginRefresh();
                }
            }

        });



        return $user;
    }

    static public function ClearExistedToken($user, $guard_key)
    {

        UtilSupport::tryQuiet(function () use ($guard_key, $user) {
            $single = static::GetGuardProviderConfig($guard_key, 'single_client_login');
            $query = $user->getAuthenticatedToken();
            $platform = request()->getClientPlatform();
            if ('all' == $single) {
                $query = $user->auth_token_all();
            } elseif ('platform' == $single) {
                $query = $user->auth_token_all($platform);
            }
//            $query && $query->delete();
            // 批量删除缓存token
            if ($query){
                $tokens = $query->get(['id','platform']);
                if ($tokens->isNotEmpty()){
                    $query->delete();
                    UtilSupport::tryQuiet(function () use ($tokens,$single) {
                        switch ($single){
                            case 'all':
                                $tokens->each(function ($item) {
                                    Cache::forget(static::GetTokenCahceKey().data_get($item,'platform').':'.data_get($item,'id'));
                                });
                                break;
                            case 'platform':
                                $tokens->each(function ($item) {
                                    Cache::forget(static::GetTokenCahceKeyPrefix().data_get($item,'id'));
                                });
                        }
                    });
                    unset($tokens);
                }
            }
        });
        return $user;
    }

    static public function LogOut($guard_key = null)
    {
        static::SetLogouting(true);
        if (!$guard = static::Guard($guard_key)) {
            return false;
        }
        //执行事件
        if (!empty(static::$callbacks_logouted_alter)) {
            foreach (static::$callbacks_logouted_alter as $callback) {
                if (!is_callable($callback)) {
                    continue;
                }
                $callback($guard);
            }
        }

        //delete token
        UtilSupport::tryQuiet(function () use ($guard) {
            $token_id = session($guard->getName().'_token_id');
            session()->forget($guard->getName() . '_token_id');
            $token = $guard->user()->getAuthenticatedToken();
            // 从缓存中删除token
            $cache_key = static::GetTokenCahceKeyPrefix().$token_id;
            Cache::forget($cache_key);
            $token && $token->delete();
        });


        UtilSupport::tryQuiet(function () use ($guard) {
            $guard->logout();
            $session = request()->session();
            if($session){
                $session_id = $session->migrate();
            }
        });

        UtilSupport::tryQuiet(function () use ($guard) {
            $guard->setUser(false);

        });

        static::SetLogouting(false);
    }

    static public function AuthenticatedUser($key = '', $guards = '')
    {
        $key = trim($key);

        $guards = $guards ? (array)$guards : [];

        $user = null;
        if (empty($guards)) {

            $guards = array_keys(config('auth.guards', []));
            $guard_key = config('auth.defaults.guard');

            if($guard_key){
                array_unshift($guards,$guard_key);
            }
        }


        foreach ($guards as  $guard_key) {
            if(array_key_exists($guard_key,static::$_authenticated_guard_user)){
                $user = UtilArr::get(static::$_authenticated_guard_user,$guard_key);
                break;
            }
            if($guard = Auth::guard($guard_key)){
                $user = $guard->user();
            }
            if($user){
                static::$_authenticated_guard_user[$guard_key] = $user;
                break;
            }
        }

        return '' === $key ? $user : data_get($user, $key, null);
    }
    static public function AuthenticatedToken($key = '', $guards = '')
    {
        $key = trim($key);

        $token = UtilSupport::tryQuiet(function ()  {
            $user = static::AuthenticatedUser();
            $token = $user->getAuthenticatedToken();
            return $token;
        });

        return '' === $key ? $token : data_get($token, $key, null);
    }

    static public function AuthenticatedUserFresh()
    {
        if (!$user = static::AuthenticatedUser()) {
            return null;
        }

        return $user->newQuery()->where($user->getAuthIdentifierName(), '=', $user->getAuthIdentifier())->first();

    }

    static public function AuthenticatedApiReturnData($user,$token=null){
        $data = [
            'user' => collect($user)->only([
                'id',
                'account',
                'name',
                'picture',
                'sex',
                'summary',
            ]),
            'token' => $token ? collect($token)->only([
                'id',
                'expire_time',
            ]) : null,
        ];

        if(is_callable($callback = static::$callbacks_authenticated_api_return_data)){
            $data = $callback($user,$token,$data);
        }
        return $data;
    }

    static public function CreateUserModel($guard_key = '')
    {
        $guard_key = trim($guard_key);
        if (!$guard_key) {
            $guard_key = static::GuardKey();
        }
        $provider = static::GuardProvider($guard_key);
        //EloquentUserProvider
        $model = UtilSupport::tryQuiet(function () use ($provider) {
            return $provider->createModel();
        });
        //DatabaseUserProvider
        if (!$model) {
            $model = UtilSupport::tryQuiet(function () use ($provider) {
                return $provider->createTable();
            });
        }
        return $model;
    }

    static public function LoadUser($credentials = [], $guard_key = '')
    {

        try {

            $model = static::CreateUserModel($guard_key);
            if (!$model) {
                throw new \Exception('配置错误');
            }

            return $model->where($credentials)->first();

        } catch (\Exception $exception) {
            $message = $exception->getMessage();
            if (!$message) {
                $message = '系统错误！';
            }
            static::SetStaticError($message);
        }
        return null;
    }

    static public function SaveUser($orgData, $id = '', $guard_key = '', $check_exists = true)
    {
        if (!$guard_key) {
            $guard_key = static::GuardKey();
        }
        try {
            $model = static::CreateUserModel($guard_key);
            if (!$model) {
                throw new \Exception('配置错误');
            }
            $fillable = (array)$model->getFillable();
            $data = collect($orgData)->only($fillable)->toArray();
            data_fill($data,'password',data_get($orgData,'password'));
            $exist_user = null;
            if ($id) {
                $exist_user = static::LoadUser(['id' => $id]);
                if (!$exist_user) {
                    throw new \Exception('用户不存在');
                }
                //unset($data['id']);
            }

            if ($check_exists) {
                $credentials = [];
                if ($exist_user) {
                    $credentials[] = [
                        'id', '!=', $exist_user->id
                    ];
                }

                $account = (string)data_get($data, 'account', '');
                if (!$exist_user && !$account) {
                    throw new \Exception('账号不能为空');
                }
                if ($account) {
                    $user = UtilSupport::tryQuiet(function () use ($credentials, $account, $guard_key) {
                        $credentials['account'] = $account;

                        return static::LoadUser($credentials, $guard_key);
                    });
                    if ($user) {
                        throw new \Exception('账号已存在');
                    }
                }


                $mobile = data_get($data, 'mobile', '');
                if ($mobile) {

                    $user = UtilSupport::tryQuiet(function () use ($credentials, $mobile, $guard_key) {
                        $credentials['mobile'] = $mobile;
                        return static::LoadUser($credentials, $guard_key);
                    });
                    if ($user) {
                        throw new \Exception('手机号已存在');
                    }
                }

                $email = data_get($data, 'email', '');
                if ($email) {
                    $user = UtilSupport::tryQuiet(function () use ($credentials, $email, $guard_key) {
                        $credentials['email'] = $email;
                        return static::LoadUser($credentials, $guard_key);
                    });

                    if ($user) {
                        throw new \Exception('邮箱已存在');
                    }
                }
            }

            $status = false;
            $user_id = '';
            if ($exist_user) {
                if(!$exist_user->created_at){
                    $data['created_at'] = Carbon::now()->toDateTimeString();
                }
                $status = $model->where('id', '=', $exist_user->id)->update($data);
                $user_id = $user_id = data_get($data, 'id',$exist_user->id);

            } else {
                if (!data_get($data, 'id')) {
                    $data['id'] = Uuid::generate();
                }
                $data['created_at'] = Carbon::now()->toDateTimeString();
                $status = $model->insert($data);
                $user_id = data_get($data, 'id');
            }


            $user = static::LoadUser(['id' => $user_id]);

            if (!$user) {
                throw new \Exception('添加用户失败');
            }
            if (!$exist_user) {
                if (!static::Registered($user,$orgData)) {
                    throw new \Exception('系统错误');
                }
            }
            return $user;
        } catch (\Exception $exception) {
            $message = $exception->getMessage();
            if (!$message) {
                $message = '系统错误！';
            }
            static::SetStaticError($message);
        }
        return false;
    }


    static public function Registered($user,$data)
    {
        //执行事件
        if (!empty(static::$callbacks_registered_alter)) {
            foreach (static::$callbacks_registered_alter as $callback) {
                if (!is_callable($callback)) {
                    continue;
                }
                if (false === $callback($user,$data)) {
                    return null;
                }
            }
        }
        return true;
    }

    static public function RegisterCheckBefore(&$data)
    {
        //执行事件
        if (!empty(static::$callbacks_register_before)) {
            foreach (static::$callbacks_register_before as $callback) {
                if (!is_callable($callback)) {
                    continue;
                }
                if (true !== ($msg = $callback($data))) {
                    return $msg;
                }
            }
        }
        return true;
    }

    /**
     * @param string $guard_key
     * @return false|AuthApiClient
     */
    static public function ProviderUseAsClient($guard_key = '')
    {
        if (!$guard_key) {
            $guard_key = static::GuardKey();
        }
        if ($client_config = static::GetGuardProviderConfig($guard_key, 'use_as_client')) {
            return new AuthApiClient($client_config);
        }
        return false;
    }

    /**
     * Token缓存前缀
     *
     * @return string
     * @author Iwtrgw 2024/3/25 09:13
     */
    static public function GetTokenCahceKeyPrefix()
    {
        return static::GetTokenCahceKey().request()->getClientPlatform().':';
    }

    /**
     * Token缓存键(不带平台前缀)
     *
     * @return string
     * @author Iwtrgw 2024/3/28 14:44
     */
    static public function GetTokenCahceKey()
    {
        return 'Auth:'.env('APP_ENV').':';
    }
}
